﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Green.AgileMapper
{
    public class DefaultAgileMapperTemplateStrategy : Green.AgileMapper.IAgileMapperTemplateStrategy
    {
        public DefaultAgileMapperTemplateStrategy()
        {
            DefaultEqualExpression = "{0}.Equals({1})";
        }
        /// <summary>
        /// 默认的from item（{0}） = domain item{1}的表达式
        /// </summary>
        public string DefaultEqualExpression
        {
            get;
            set;
        }
        #region warp from
        public virtual string GetFromToToFunction(Type fromtype, Type totype, Green.AgileMapper.IMappingRule[] rules)
        {
            if (rules != null)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("var fromObj = new {0}();\r\n", fromtype.FullName);

                var simpleMappings = rules.Where(t => t is SimpleMappingRule).OfType<SimpleMappingRule>().ToList();
                sb.Append(GeneratorSimpleMappingWarpFromCode(fromtype, totype, simpleMappings));

                var expressionMappings = rules.Where(t => t is ExpressionMappingRule).OfType<ExpressionMappingRule>().ToList();
                sb.Append(GeneratorExpressionMappingWarpFromCode(fromtype, totype, expressionMappings));


                var objectMappings = rules.Where(t => t is ObjectMappingRule).OfType<ObjectMappingRule>().ToList();
                sb.Append(GeneratoObjectMappingWarpFromCode(fromtype, totype, objectMappings));

                var collectionMappings = rules.Where(t => t is CollectionMappingRule).OfType<CollectionMappingRule>().ToList();
                sb.Append(GeneratoCollectionMappingWarpFromCode(fromtype, totype, collectionMappings));

                sb.AppendFormat("return fromObj;");
                return sb.ToString();
            }
            return "return null;";
        }

        private string GeneratoCollectionMappingWarpFromCode(Type fromtype, Type totype, List<CollectionMappingRule> collectionMappings)
        {
            StringBuilder sb = new StringBuilder();
            collectionMappings.ForEach(t =>
            {
                if (t.IsConvertFrom)
                {
                    var fromCollectionType = Green.FastInvoker.PropertyAccessor.GetPropertyType(fromtype, t.FromPoperty);
                    var fromGenType = fromCollectionType.GetGenericArguments()[0];
                    sb.AppendFormat("if( domainObj.{0}!=null )\r\n{{\r\n", t.ToPoperty);
                    if (!CollectionMappingRule.IsPrimitivePredicate(fromGenType))
                    {
                        sb.AppendFormat("fromObj.{0} = domainObj.{1}.Warp();\r\n", t.FromPoperty, t.ToPoperty);
                    }
                    else
                    {
                        sb.AppendFormat("fromObj.{0} = new List<{1}>();\r\n", t.FromPoperty, fromGenType.FullName);
                        sb.AppendFormat(@"foreach (var item_{1} in domainObj.{1})
{{
    fromObj.{0}.Add(item_{1});
}}", t.FromPoperty, t.ToPoperty);
                        sb.AppendLine();

                    }
                    sb.Append("}\r\n");
                }

            });
            return sb.ToString();
        }

        private string GeneratoObjectMappingWarpFromCode(Type fromtype, Type totype, List<ObjectMappingRule> objectMappings)
        {
            StringBuilder sb = new StringBuilder();
            objectMappings.ForEach(t =>
            {
                sb.AppendFormat("if( domainObj.{0}!=null )\r\n{{\r\n", t.ToPoperty);
                sb.AppendFormat("fromObj.{0} = domainObj.{1}.Warp();\r\n", t.FromPoperty, t.ToPoperty);
                sb.Append("}\r\n");

            });
            return sb.ToString();
        }

        private string GeneratorExpressionMappingWarpFromCode(Type fromtype, Type totype, List<ExpressionMappingRule> expressionMappings)
        {
            StringBuilder sb = new StringBuilder();
            expressionMappings.ForEach(smap =>
            {
                var obj = smap.Expression.ToString();
                var fromPropertyType = Green.FastInvoker.PropertyAccessor.GetPropertyType(fromtype, smap.FromPoperty);
                sb.AppendFormat("fromObj.{0} = {1};\r\n", smap.FromPoperty, smap.Expression);
                //sb.AppendFormat("fromObj.{0} = Green.Utility.DynamicExpression.Compute<{2}>(domainObj, \"{1}\");\r\n", smap.FromPoperty, smap.Expression.Replace("\"", "\\\""), fromPropertyType.FullName);
            });
            return sb.ToString();
        }

        private string GeneratorSimpleMappingWarpFromCode(Type fromtype, Type totype, List<SimpleMappingRule> simpleMappings)
        {
            StringBuilder sb = new StringBuilder();

            for (var i = 0; i < simpleMappings.Count; i++)
            {
                var smap = simpleMappings[i];
                if (smap.IsConvertFrom)
                {
                    var toPropertySplits = smap.ToPoperty.Split('.');
                    if (toPropertySplits.Length == 1)
                    {
                        sb.AppendFormat("fromObj.{0} = domainObj.{1};\r\n", smap.FromPoperty, smap.ToPoperty);
                    }
                    else if (toPropertySplits.Length == 2)
                    {
                        sb.AppendFormat("if( domainObj.{0}!=null )\r\n{{\r\n", toPropertySplits[0]);

                        simpleMappings.Where(t => t.ToPoperty.StartsWith(toPropertySplits[0] + "."))
                            .ToList().ForEach(t =>
                            {
                                sb.AppendFormat("fromObj.{0} = domainObj.{1};\r\n", t.FromPoperty, t.ToPoperty);
                                simpleMappings.Remove(t);
                            });

                        sb.Append("}\r\n");
                    }
                }

            }
            return sb.ToString(); ;
        }
        #endregion

        #region warp to
        public virtual string GetToToFromFunction(Type fromtype, Type totype, Green.AgileMapper.IMappingRule[] rules)
        {
            if (rules != null)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("if(fromObj == null )\r\n{{\r\n return ;\r\n}}\r\n", totype.FullName);

                sb.AppendFormat("if(domainObj == null )\r\n{{\r\n domainObj = new {0}();\r\n}}\r\n", totype.FullName);

                var simpleMappings = rules.Where(t => t is SimpleMappingRule).OfType<SimpleMappingRule>().ToList();
                sb.Append(GeneratorSimpleMappingWarpToCode(fromtype, totype, simpleMappings));

                var expressionMappings = rules.Where(t => t is ExpressionMappingRule).OfType<ExpressionMappingRule>().ToList();
                sb.Append(GeneratorExpressionMappingWarpToCode(fromtype, totype, expressionMappings));


                var objectMappings = rules.Where(t => t is ObjectMappingRule).OfType<ObjectMappingRule>().ToList();
                sb.Append(GeneratoObjectMappingWarpToCode(fromtype, totype, objectMappings));

                var collectionMappings = rules.Where(t => t is CollectionMappingRule).OfType<CollectionMappingRule>().ToList();
                sb.Append(GeneratoCollectionMappingWarpToCode(fromtype, totype, collectionMappings));

                return sb.ToString();
            }
            return "";
        }

        private string GeneratoCollectionMappingWarpToCode(Type fromtype, Type totype, List<CollectionMappingRule> collectionMappings)
        {
            StringBuilder sb = new StringBuilder();
            collectionMappings.ForEach(t =>
            {
                if (t.IsConvertTo)
                {
                    var toCollectionType = Green.FastInvoker.PropertyAccessor.GetPropertyType(totype, t.ToPoperty);
                    var toGenType = toCollectionType.GetGenericArguments()[0];
                    sb.AppendFormat("if( fromObj.{0}!=null )\r\n{{\r\n", t.FromPoperty);
                    sb.AppendFormat("if( domainObj.{0} == null )\r\n{{\r\ndomainObj.{0} = new List<{1}>();\r\n}}\r\n", t.ToPoperty, toGenType.FullName);
                    if (!CollectionMappingRule.IsPrimitivePredicate(toGenType))
                    {
                        string equalExpression = "(fromObjItem, domainObjItem) => fromObjItem.Equals(domainObjItem)";
                        if (!string.IsNullOrEmpty(t.EqualExpression))
                        {
                            equalExpression = string.Format("(fromObjItem, domainObjItem) => {0}", t.EqualExpression);
                        }
                        else if (!string.IsNullOrEmpty(DefaultEqualExpression))
                        {
                            equalExpression = string.Format(DefaultEqualExpression, "fromObjItem", "domainObjItem");
                        }

                        sb.AppendFormat("fromObj.{0}.Warp(domainObj.{1},{3},{2});\r\n", t.FromPoperty, t.ToPoperty, t.IsDeleteNotInFromItem ? "true" : "false", equalExpression);
                    }
                    else
                    {
                        sb.AppendFormat("domainObj.{0}.Clear();\r\n", t.ToPoperty);

                        sb.AppendFormat(@"foreach (var item_{1} in fromObj.{0})
{{
    domainObj.{1}.Add(item_{1});
}}", t.FromPoperty, t.ToPoperty);
                        sb.AppendLine();

                    }
                    sb.Append("}\r\n");

                }

            });
            return sb.ToString();
        }

        private string GeneratoObjectMappingWarpToCode(Type fromtype, Type totype, List<ObjectMappingRule> objectMappings)
        {
            StringBuilder sb = new StringBuilder();
            objectMappings.ForEach(t =>
            {
                var toPropertyType = Green.FastInvoker.PropertyAccessor.GetPropertyType(totype, t.ToPoperty);
                sb.AppendFormat("if( domainObj.{0} == null )\r\n{{\r\n domainObj.{0} = new {1}();\r\n}}\r\n", t.ToPoperty, toPropertyType.FullName);
                sb.AppendFormat("fromObj.{0}.Warp(domainObj.{1});\r\n", t.FromPoperty, t.ToPoperty);
                // sb.Append("}\r\n");

            });
            return sb.ToString();
        }

        private string GeneratorExpressionMappingWarpToCode(Type fromtype, Type totype, List<ExpressionMappingRule> expressionMappings)
        {
            StringBuilder sb = new StringBuilder();

            return sb.ToString();
        }

        private string GeneratorSimpleMappingWarpToCode(Type fromtype, Type totype, List<SimpleMappingRule> simpleMappings)
        {
            StringBuilder sb = new StringBuilder();

            for (var i = 0; i < simpleMappings.Count; i++)
            {
                var smap = simpleMappings[i];
                if (smap.IsConvertTo)
                {
                    var toPropertySplits = smap.ToPoperty.Split('.');
                    if (toPropertySplits.Length == 1)
                    {
                        sb.AppendFormat("domainObj.{1} = fromObj.{0} ;\r\n", smap.FromPoperty, smap.ToPoperty);
                    }
                    else if (toPropertySplits.Length == 2)
                    {
                        var domainObjFirstType = Green.FastInvoker.PropertyAccessor.GetPropertyType(totype, toPropertySplits[0]);

                        sb.AppendFormat("if( domainObj.{0} == null )\r\n{{\r\n domainObj.{0} = new {1}();\r\n}}\r\n", toPropertySplits[0], domainObjFirstType.FullName);

                        simpleMappings.Where(t => t.ToPoperty.StartsWith(toPropertySplits[0] + "."))
                            .ToList().ForEach(t =>
                            {
                                sb.AppendFormat("domainObj.{1} = fromObj.{0};\r\n", t.FromPoperty, t.ToPoperty);
                                simpleMappings.Remove(t);
                            });

                        sb.AppendLine();
                    }
                }
            }
            return sb.ToString();
        }
        #endregion

        #region IAgileMapperTemplateStrategy 成员

        public string CodeGenerator(Type fromtype, Type totype, IMappingRule[] rules)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat(@"using System;
using System.Linq;
using System.Data;
using System.Collections.Generic;

namespace Green.AgileMapper
{{
     public static partial class AgileMapperMapping
    {{
        /// <summary>
        /// {0}  Warp fromObj {1};
        /// </summary>
        /// <param name=""domainObj"">{1}</param>
        /// <returns>{0}</returns>
        public static  {0} Warp(this {1} domainObj)
        {{
           {2}
        }}
        
        /// <summary>
        /// {0}  Warp domainObj {1};
        /// </summary>
        /// <param name=""domainObj"">{1}</param>
        /// <returns>fromObj</returns>
        public static void Warp(this {0} fromObj, {1} domainObj)
        {{
			{3}
        }}

        /// <summary>
        /// {0} collection Warp fromObj {1} collection;
        /// </summary>
        /// <param name=""form"">{0} collection</param>
        /// <param name=""domainObj"">{1} collection</param>        
        public static List<{0}> Warp(this IList< {1}> domainObj)
        {{
            List<{0}> froms = new List<{0}>();
            domainObj.ToList().ForEach(t =>
            {{
                froms.Add(Warp(t));
            }});
            return froms;
        }}

        /// <summary>
        /// {0} collection Warp domainObj {1} collection;
        /// </summary>
        /// <param name=""fromObj"">{0} collection</param>
        /// <param name=""domainObj"">{1} collection</param>      
         public static void Warp(this IList<{0}> fromObj, IList<{1}> domainObj)
         {{
               fromObj.Warp(domainObj,(fromObjItem,domainObjItem) => {4},false);
         }}   

        /// <summary>
        /// {0} collection Warp domainObj {1} collection;
        /// </summary>
        /// <param name=""fromObj"">{0} collection</param>
        /// <param name=""domainObj"">{1} collection</param>
        /// <param name=""isDeleteNotInFromItem"">Delete the item that not in From collection</param>           
         public static void Warp(this IList<{0}> fromObj, IList<{1}> domainObj,bool isDeleteNotInFromItem)
         {{
               fromObj.Warp(domainObj,(fromObjItem,domainObjItem) => {4},isDeleteNotInFromItem);
         }}   

        /// <summary>
        /// {0} collection Warp domainObj {1} collection;
        /// </summary>
        /// <param name=""fromObj"">{0} collection</param>
        /// <param name=""domainObj"">{1} collection</param>
        /// <param name=""equalPredicate"">the from item equal to item expression</param>           
         public static void Warp(this IList<{0}> fromObj, IList<{1}> domainObj,Func<{0}, {1}, bool> equalPredicate)
         {{
               fromObj.Warp(domainObj,equalPredicate,false);
         }}   

        /// <summary>
        /// {0} collection Warp domainObj {1} collection;
        /// </summary>
        /// <param name=""fromObj"">{0} collection</param>
        /// <param name=""domainObj"">{1} collection</param> 
        /// <param name=""equalPredicate"">the from item equal to item expression</param>      
        /// <param name=""isDeleteNotInFromItem"">Delete the item that not in From collection</param>     
         public static void Warp(this IList<{0}> fromObj, IList<{1}> domainObj, Func<{0}, {1}, bool> equalPredicate,bool isDeleteNotInFromItem)
         {{
             if(fromObj==null)
             {{
                return ;
             }}

             if (domainObj == null)
             {{
                 domainObj = new List< {1}>();
             }}

             fromObj.ToList().ForEach(fromObjItem =>
             {{
                 {1} toItem = default({1});
                 if (equalPredicate != null)
                 {{
                     toItem = domainObj.SingleOrDefault(domainObjItem => equalPredicate(fromObjItem,domainObjItem));
                 }}
                 if (toItem == null)
                 {{
                     toItem = new {1}();
                     domainObj.Add(toItem);
                 }}
                 Warp(fromObjItem, toItem);
             }});
            
            if (isDeleteNotInFromItem)
            {{
                domainObj.Where(domainObjItem => fromObj.FirstOrDefault(fromObjItem => equalPredicate(fromObjItem, domainObjItem)) == null)
                    .ToList().ForEach(t =>
                    {{
                        domainObj.Remove(t);
                    }});
            }}
         }}
    }}
}}", fromtype, totype, GetFromToToFunction(fromtype, totype, rules), GetToToFromFunction(fromtype, totype, rules), string.Format(DefaultEqualExpression, "fromObjItem", "domainObjItem"));
            return sb.ToString();
        }

        #endregion
    }
}
